论文《Ditto:Elastic Confidential VMs with Secure and Dynamic CPU Scaling》总结
本文将介绍 2024 年发表在 arXiv 上的论文《DITTO:Elastic Confidential VMs with Secure and Dynamic CPU Scaling》。
解决的问题
机密虚拟机(CVM)在带来了强大的机密性和完整性保护的同时,也带来了很多限制,导致虚拟机的性能和灵活性的下降。例如:不支持 vCPU 的热插拔(即运行中动态调整 vCPU 的数量),该特性可以用于在虚拟机运行过程中灵活调整计算能力,应用于 Serverless 等计算环境下。
虽然的商用 CVM 方案还没有任何一家支持 vCPU 热插拔,但是内存的动态调整是可行的。例如 AMD SEV-SNP 下 hypervisor 可以使用
RMPUPDATE
指令将 CVM 的内存进行回收和动态分配。
由于缺少了 vCPU 数量的动态调整能力,现有的机密无服务器环境(OpenWhisk + Kubernetes + 机密容器)要想动态调整运算能力,只能借助于启动新的 CVM,这会带来很大的性能开销。本文提出了“弹性 CVM” 和 “Woker vCPU” 的概念,能够在 CVM 环境下动态调整参与计算的 vCPU 数量。具体来说,本文的主要贡献如下:
- 弹性 CVM 的概念: 利用 CVM 和 hypervisor 的协同来动态调整 CPU 资源的分配,增强 CVM 的效率。
- 创新的 Worker vCPU 设计: Worker vCPU 是一种特殊的 vCPU,通过与 hypervisor 协同的方式被调度,能够随着工作负载的变化而在休眠和工作状态间转变。
- Worker vCPU 抽象层: 用于简化对 Worker vCPU 的操作。
- Ditto 原型开发和实验评估: Ditto 是使用了 Worker vCPU 设计的机密 Serverless 平台,能够实现安全且自动扩展的 Serverless 环境。实验评估表明 Ditto 在资源利用上相较于现有的机密 Serverless 平台有显著提升。
设计与实现
Worker vCPU
Woker vCPU 的架构图如下所示:
Worker vCPU 的状态分为活跃和休眠,设计目标是根据系统负载的变化而动态改变 Woker vCPU 的状态。
为了实现的简单和减少 CVM 内 TCB(可信计算基) 的大小,作者选择将对 Worker vCPU 的调度策略放在了 hypervisor 中,而不是由 CVM 内部来决定。而 hypervisor 对 CVM 内部执行情况的了解是很有限的,因此 Worker vCPU 的调度需要 CVM 与 hypervisor 在不损害安全性的前提下完成,且适用的场景没有那么广。主要适用场景为:事件驱动系统和生产者-消费者模型(无状态和松耦合线程),例如 HTTP 请求的发送和处理、数据库查询的请求和处理。这些请求的处理相对独立,能够动态调整计算资源。
初始化 CVM 时,需要指定普通的 vCPU 数量 $m$ 和最大的 Woker vCPU 数量 $n$ ,Woker vCPU 在 CPU 硬件看来与 vCPU 无异,但是为了实现动态运行时调整,CVM 内核和 hypervisor 都必须能够对此进行区分。例如,可以将 $vCPU[1, m]$ 看作是普通 vCPU, $vCPU[m + 1, m + n]$ 看作是 Woker vCPU,CVM 在启动应用时将特定的工作线程绑定到特定的 Woker vCPU 上。
由于 CVM 下虚拟机内部的运行状态对 hypervisor 来说是不可见的(SEV 的内存加密和 SEV-ES 的寄存器状态加密),因此 CVM 与 hypervisor 的协同很重要。例如:Worker vCPU 可以在执行完一个任务后,主动向 hypervisor 发送 “check-in” 的信号,表明一个任务已完成,hypervisor 受到信号后便可以决定要不要将 Worker vCPU 置为休眠状态。之所以需要这样的协同,是为了防止 hypervisor 在一个任务执行中途将 Worker vCPU 置为休眠,导致任务执行被推迟。
Ditto
Ditto 的架构图如下所示:
Ditto 相较于传统的 Kata 容器的部署过程,存在下列主要不同:
- 启动 CVM: 以 $m$ 个普通 vCPU 和 $n$ 个 Worker vCPU 进行初始化。
- Worker vCPU 注册: 应用启动时需要将特定的线程注册到 Worker vCPU 上,且保持不变,同时还不允许其运行内核函数,因此 Worker vCPU 可以被安全的启用和睡眠,不会影响整体系统的正常工作,只会影响效率。
Worker vCPU 调度
调度器依赖于两个方面: 观察指标和调度算法 。
在 CVM 环境下,hypervisor 的观察指标很有限。在 Ditto 中,作者主要基于 Linux 内核数据结构 task_struct
的时间信息来计算一个采样周期内 vCPU 运行的时间来计算工作负载量。
这里信息的获取方式不太明白。
其他一些可能的指标还有:HTTP 请求的数量、每个请求的近似处理时间等。
调度算法方面,作者采用了一个简单的策略:当活跃的 vCPU 的总负载达到一个预先设定的阈值后,就唤醒一个 Worker vCPU,活跃的 vCPU 的总负载降低过一个预先设定的阈值后,就睡眠一个 Worker vCPU。注意如前文所述,需要在接收到 CHECKIN
请求后才能睡眠,防止中断任务执行。如果所有的 Worker vCPU 都不足以应对工作负载,可以考虑启动新的 CVM。
运行时控制
为了方便实现对 Worker vCPU 的动态控制,需要定义一套的 CVM-hypervisor 通信协议。通信基于预先定义的 CPUID
实现。在 SEV-ES 中,CPUID
指令会触发 #VC
异常,会陷入到 guest 内核的 VC 处理程序中,执行必要的检查,并将需要向 hypervisor 提供的信息(信息类型和信息参数等)通过共享的 GHCB 内存块传递,并退出虚拟机回到 hypervisor 进行处理。下图是作者定义的一个简单的通讯协议: